Skip to content

[PM-12436] replace node keytar - #1012

Merged
JaredScar merged 125 commits into
mainfrom
ac/pm-12436-replace-node-keytar-sonnet-attempt
Apr 10, 2026
Merged

[PM-12436] replace node keytar #1012
JaredScar merged 125 commits into
mainfrom
ac/pm-12436-replace-node-keytar-sonnet-attempt

Conversation

@JaredScar

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-12436

📔 Objective

Rid of node keytar from Directory Connector

BTreston and others added 18 commits January 29, 2026 14:36
…rate from keytar. Update .gitignore for Rust artifacts, adjust package.json for new build scripts, and modify workflows for native module compilation. Enhance state versioning to support migration of credentials from keytar to desktop_core.
…Script configurations and package dependencies. Refactor native binding logic and enhance error handling. Remove legacy keytar references and adjust package-lock.json for new native module structure.
…removing unnecessary semicolons. Update build scripts in package.json to exclude JavaScript during native module compilation.
…ule directly in the renderer process, removing the need for the credential storage listener. Update TypeScript definitions and enhance error handling in password management functions. Adjust Cargo dependencies and versions for improved compatibility.
@JaredScar
JaredScar requested a review from a team as a code owner February 24, 2026 21:59
@JaredScar JaredScar added the ai-review Request a Claude code review label Feb 24, 2026
@claude

claude Bot commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @BTreston's task in 6m 1s —— View job


Code Review: PM-12436 replace node keytar

  • Analyze PR diff against origin/restructure-repo
  • Review all changed files for correctness, security, and best practices
  • Check status of previously flagged issues
  • Post review findings

Overall Assessment: REQUEST CHANGES (due to existing unresolved findings)

This PR replaces the deprecated keytar Node module with a custom Rust-based native module (dc-native) backed by Bitwarden's desktop_core for OS keychain access. The change restructures the repository (moving code into libs/, src-cli/, and src-gui/), rewrites the state service to a flat key-value model, introduces state migration from v3/v4 through v5 to v6, and adds comprehensive test coverage for the new state and migration services. The native module handles credential CRUD operations and includes a Windows-specific migration from keytar's UTF-8 encoding to desktop_core's UTF-16 encoding.

No new findings beyond those already flagged in prior reviews. Three existing unresolved threads remain and should be addressed before merge.

Active Issues (3 unresolved threads)
  • CRITICAL: Windows users migrating from state v3/v4 will read garbled credentials because the v3-to-v5 migration reads keytar-stored values through dc-native (which expects UTF-16) before the v5-to-v6 encoding migration runs
    • libs/services/state-service/stateMigration.service.ts:50-52existing thread
  • CRITICAL: The v5-to-v6 keytar encoding migration builds legacy key names using the new SecureStorageKeys.* values (e.g. {userId}_secretLdap) instead of the actual old key names (e.g. {userId}_ldapPassword), so old credentials on Windows are never re-encoded
    • libs/services/state-service/stateMigration.service.ts:248-275existing thread
  • CRITICAL: The native/index.js namespace export alignment concern (thread is outdated — worth verifying resolution after recent commits)
Previously Flagged — Now Resolved (13 threads)
  • native/index.js NAPI namespace mismatch — Fixed
  • PASSWORD_NOT_FOUND now exported from Rust via #[napi]Fixed
  • GUI ElectronRendererSecureStorageService updated to use dc-native directly — Fixed
  • webpack.main.cjs includes dc-native in externals — Fixed
  • nativeSecureStorage.service.ts import path corrected — Fixed
  • Rust scopeguard placement in migration.rsFixed
  • build:dist script no longer references removed reset script — Fixed
  • .claude/settings.json accidental merge reverted — Fixed
  • ESLint import ordering in nativeSecureStorage.service.tsFixed
  • Stray closing brace in migration.rsFixed
  • migrateKeytarPassword now wired into state migration pipeline — Fixed
  • Credential migration coverage expanded — Fixed
  • Shared APPLICATION_NAME constant extracted — Fixed

@github-actions

github-actions Bot commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Logo
Checkmarx One – Scan Summary & Details12f4add0-87a5-4d2c-9774-0e1ce6824711

Great job! No new security vulnerabilities introduced in this pull request

Comment thread native/index.js Outdated
Comment thread src/services/stateMigration.service.ts Outdated
Comment thread jslib/electron/src/services/electronRendererSecureStorage.service.ts Outdated
@codecov

codecov Bot commented Feb 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (bb9e98b) to head (8ccbbd8).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@     Coverage Diff      @@
##   main   #1012   +/-   ##
============================
============================

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… migration logic

- Upgraded `@yao-pkg/pkg` to version `^6.14.2` and updated related dependencies in `package-lock.json`.
- Modified CLI packaging scripts in `package.json` to target Node.js 22 for Windows, macOS, and Linux.
- Enhanced state migration service to improve handling of secure storage keys, ensuring compatibility with previous versions.
- Adjusted GitHub Actions workflow to package the CLI for macOS ARM64 architecture.
- Removed the `ElectronRendererSecureStorageService` and replaced it with `NativeSecureStorageService` for improved secure storage handling.
- Updated the service module to use the new storage service, ensuring compatibility with the application name constant.
- This change simplifies the secure storage logic and enhances maintainability.
- Added a try-catch block to handle JSON parsing errors when retrieving stored values.
- Improved logging to provide clearer error messages when parsing fails, returning null in such cases.
- Added coverage-ldap directory to ESLint ignore list.
- Enhanced error logging in NativeSecureStorageService for better clarity on JSON parsing failures.
- Cleaned up whitespace in stateMigration.service.ts and services.module.ts for improved readability.
@eliykat
eliykat self-requested a review April 8, 2026 23:32
Comment thread libs/models/state.model.ts Outdated
Comment thread package.json Outdated
Comment thread package.json
Comment thread libs/services/nativeSecureStorage.service.ts Outdated
Comment thread libs/services/state-service/stateMigration.service.ts Outdated
Comment thread libs/services/state-service/stateMigration.service.ts Outdated
Comment thread native/src/migration.rs
Comment thread .github/workflows/build.yml Outdated
Comment thread package.json Outdated
Comment thread native/src/migration.rs Outdated
- Replaced manual file watching with electron-reload for improved performance and simplicity.
- Removed unnecessary fs.watch implementation, streamlining the codebase.
- Updated CLI packaging scripts in `package.json` to target platform-specific identifiers for Windows, macOS, and Linux.
- Removed the macOS ARM64 build step from the GitHub Actions workflow to streamline the CI process.
- Adjusted the version of `@yao-pkg/pkg` in `package.json` for compatibility.
- Cleaned up comments in `state.model.ts` for better clarity.
- Simplified the migration process from keytar's UTF-8 format to desktop_core's UTF-16 format.
- Introduced a new helper function `get_password_keytar` to handle credential retrieval.
- Improved error handling and streamlined the migration function to enhance readability and maintainability.
Comment thread native/src/migration.rs
Comment on lines +38 to +43
let password = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT (Safety): from_utf8_unchecked is undefined behavior in Rust if the credential blob contains any non-UTF-8 bytes.

Details and fix

If the Windows Credential Manager entry is corrupted or was written by something other than keytar, the blob may not be valid UTF-8. Using from_utf8_unchecked in that case is UB per the Rust reference, potentially causing memory safety violations.

Replace with the checked variant:

Suggested change
let password = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
};
let password = unsafe {
std::str::from_utf8(std::slice::from_raw_parts(
(*credential).CredentialBlob,
(*credential).CredentialBlobSize as usize,
))
.map_err(|e| anyhow::anyhow!("Credential blob is not valid UTF-8: {}", e))?
};

This returns an error instead of UB when the blob is not valid UTF-8, allowing migrate_keytar_password to gracefully skip that credential (since it catches Err).

Comment thread package-lock.json Outdated
- Downgraded `@yao-pkg/pkg` to version `6.5.1` for compatibility.
- Updated related dependencies in `package-lock.json` to ensure stability.
- Simplified the state migration logic by removing unnecessary keytar password migration code, while retaining essential key mappings for secure storage.
@JaredScar
JaredScar requested review from BTreston and eliykat April 9, 2026 20:07
- Moved the import of `passwords` from "dc-native" to a more appropriate location within the file for better organization and readability.

@eliykat eliykat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Just 2 things to action below (do not do the rust change in this PR).

Also:

Comment thread src-gui/main.ts Outdated
Comment thread libs/services/state-service/stateMigration.service.ts Outdated
Comment thread native/src/migration.rs

pub async fn migrate_keytar_password(service: &str, account: &str) -> Result<bool> {
let value = match get_password_keytar(service, account) {
Err(_) => return Ok(false),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to return the error, but let's do that in a smaller follow-up PR so as not to block this one.

- Removed unnecessary storage keys from the state migration service to streamline the migration process.
- Updated the main.ts file to disable ESLint for the electron-reload import, improving code clarity while maintaining functionality.
Comment on lines +43 to +44
- name: Build native module
run: npm run build:native

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT (Build): Native module build will fail -- missing Rust toolchain setup and system dependencies

Details and fix

The build.yml workflow installs both the Rust toolchain (dtolnay/rust-toolchain@stable) and Linux system dependencies (libdbus-1-dev, libsecret-1-dev, pkg-config) before building the native module. This workflow does neither.

napi build invokes cargo build, which requires the Rust compiler and linker access to libsecret-1-dev and libdbus-1-dev headers. While the GitHub-hosted runner may have a Rust toolchain cached, it does not include the -dev header packages needed for compilation.

Add the missing setup steps before the native build:

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable

      - name: Set up system dependencies
        run: |
          sudo apt-get update
          sudo apt-get -y install libdbus-1-dev libsecret-1-dev pkg-config

      - name: Build native module
        run: npm run build:native

- Updated the Node.js setup action to remove the installation of node-gyp, simplifying the process.
- Enhanced build triggers in `build-pr.yml` and `build-target.yml` to include additional paths for better coverage during CI.
- Changed the parameter for CRED_FLAGS from a direct value to an Option type for improved type safety and clarity in the credential retrieval process.
@sonarqubecloud

Copy link
Copy Markdown

@JaredScar
JaredScar requested a review from eliykat April 10, 2026 01:33
@JaredScar

Copy link
Copy Markdown
Contributor Author

As a note, the builds will fail via the GitHub checks because it pulls in the build.yml from the main branch rather than using the existing yml on this branch with the valid changes

@JaredScar
JaredScar merged commit 49d3dd8 into main Apr 10, 2026
28 of 32 checks passed
@JaredScar
JaredScar deleted the ac/pm-12436-replace-node-keytar-sonnet-attempt branch April 10, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants